package com.mingliqiye.proxy.tcp;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;

@Slf4j
public class PorxyItem {
    private final InetSocketAddress serverInetAddress;
    private final Socket fromSocket;
    private final Socket toSocket;
    private final Porxy father;
    @Getter
    private final String id;

    PorxyItem(Socket fromSocket, InetSocketAddress serverInetAddress, Porxy father) {
        this.father = father;
        this.fromSocket = fromSocket;
        this.serverInetAddress = serverInetAddress;
        this.id = "%s:%s".formatted(fromSocket.getInetAddress().getHostAddress(), fromSocket.getPort());
        toSocket = new Socket();
    }

    private void forwardData(InputStream input, OutputStream output, Boolean isfork) {
        byte[] buffer = new byte[10240];
        int bytesRead;
        while (true) {
            try {
                if ((bytesRead = input.read(buffer)) == -1)
                    break;
                output.write(buffer, 0, bytesRead);
                output.flush();
                if (isfork) {
                    log.info("new proxy {}:{} <- {}:{}  len:{}", fromSocket.getInetAddress().getHostAddress(), fromSocket.getPort(), toSocket.getInetAddress().getHostAddress(), toSocket.getPort(), bytesRead);
                } else {
                    log.info("new proxy {}:{} -> {}:{}  len:{}", fromSocket.getInetAddress().getHostAddress(), fromSocket.getPort(), toSocket.getInetAddress().getHostAddress(), toSocket.getPort(), bytesRead);
                }
            } catch (
                    SocketTimeoutException e) {
            } catch (
                    IOException e) {
                try {
                    close();
                    input.close();
                    output.close();
                    break;
                } catch (
                        IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
        close();
    }

    public void close() {
        try {
            fromSocket.close();
            toSocket.close();
        } catch (
                IOException e) {
            throw new RuntimeException(e);
        }
        father.removerClinet(this.id);
    }

    public void start() {
        try {
            toSocket.connect(serverInetAddress, 1000);
            toSocket.setSoTimeout(10);
            fromSocket.setSoTimeout(10);
            Thread a = new Thread(() -> {
                try {
                    forwardData(toSocket.getInputStream(), fromSocket.getOutputStream(), true);
                } catch (
                        IOException e) {
                    throw new RuntimeException(e);
                }
            });
            a.setName("<-");
            a.start();
            Thread b = new Thread(() -> {
                try {
                    forwardData(fromSocket.getInputStream(), toSocket.getOutputStream(), false);
                } catch (
                        IOException e) {
                    throw new RuntimeException(e);
                }
            });
            b.setName("->");
            b.start();
        } catch (
                IOException e) {
            log.error("Failed to connect to the remote {}:{}", serverInetAddress.getHostString(), serverInetAddress.getPort());
            log.error("Kick out client {}", this.id);
            this.close();
        }
    }
}
